﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CustomUvMapper : ScriptableObject
{
    [Tooltip("调节偏离中央经线经度60度的位置")]
    public float x_scale_1_3 = 1.06f;
    [Tooltip("调节偏离中央经线120度的位置")]
    public float x_scale_2_3 = 1.03f;
    [Tooltip("调节中央经线上纬度30度的位置")]
    public float y_scale_30 = 0.97f;
    [Tooltip("调节中央经线上纬度60度的位置")]
    public float y_scale_60 = 0.974f;
    [Tooltip("调节纬度30度的弯曲度, 0为不弯曲")]
    public float latitudeDistort30 = 0.58f;
    [Tooltip("调节纬度60度的弯曲度，0为不弯曲")]
    public float latitudeDistort60 = 1.1f;
    [Tooltip("调节纬度10度的弧长")]
    public float p10 = 0.989f;
    [Tooltip("调节回归线的弧长")]
    public float p23 = 0.954f;
    [Tooltip("调节纬度30度的弧长")]
    public float p30 = 0.931f;
    [Tooltip("调节纬度40度的弧长")]
    public float p40 = 0.887f;
    [Tooltip("调节纬度50度的弧长")]
    public float p50 = 0.83f;
    [Tooltip("调节纬度60度的弧长")]
    public float p60 = 0.765f;
    [Tooltip("调节极圈的弧长")]
    public float p66 = 0.711f;
    [Tooltip("调节纬度75度的弧长")]
    public float p75 = 0.634f;

    public Vector2 ToTarget(Vector2 origin)
    {
        
        //x方向伸缩
        {
            var polynomial = GetFunctionSolution(
                    (1, 0),
                    (x_scale_1_3, 1f/6),
                    (x_scale_2_3, 1f/3),
                    (1, 0.5f)
                );
            var x = Mathf.Abs(origin.x - 0.5f);
            //fx为计算的缩放尺度
            var fx = polynomial.a * x * x * x + polynomial.b * x * x + polynomial.c * x + polynomial.d;
            if(x_scale_2_3 != 1 || x_scale_1_3 != 1)
                origin.x = Mathf.Sign(origin.x - 0.5f) * x * fx + 0.5f;
        }

        //y方向伸缩
        {
            var polynomial = GetFunctionSolution(
                     (1, 0),
                     (y_scale_30, 1f / 6),
                     (y_scale_60, 1f / 3)
                 );
            var x = Mathf.Abs(origin.y - 0.5f);
            //fx为计算的缩放尺度
            var fx = polynomial.a * x * x + polynomial.b * x  + polynomial.c;
            if (y_scale_30 != 1 || y_scale_60 != 1)
                origin.y = Mathf.Sign(origin.y - 0.5f) * x * fx + 0.5f;
        }

        if (latitudeDistort30 == 0)
        {
            return origin;
        }
        else
        {
            var delta = origin.y - 0.5f;
            var absd = Mathf.Abs(delta);
            if (absd < 0.001f || latitudeDistort30 == 0 && latitudeDistort60 == 0)
            {
                return origin;
            }
            else
            {
                // a * x * x + b * x = fx
                // a + 6 b = 36 dist30
                // a + 3 b = 9  dist60
                var b = (36 * latitudeDistort30 - 9 * latitudeDistort60) / 3;
                var a = 36 * latitudeDistort30 - 6 * b;
                var divider = a * absd * absd + b * absd;
                if (Mathf.Abs(divider)<0.001f)
                    return origin;

                var radius = 1 / divider;

                (float a, float b, float c, float d) polynomial;

                if (absd < 0.5f * 30 / 90)
                //纬度0~30度
                {
                    polynomial = GetFunctionSolutionInDegree((1, 0), (p10, 10), (p23, 23 + 26f/60), (p30, 30));

                }
                else if(absd < 0.5f * 60 / 90)
                //纬度30~60度
                {
                    polynomial = GetFunctionSolutionInDegree((p30, 30), (p40, 40), (p50, 50), (p60, 60));
                }
                else
                //纬度60~90度
                {
                    polynomial = GetFunctionSolutionInDegree( (p60, 60), (p66, 66 + 34f/60), (p75, 75), (0.5f, 90));
                }

                float totalAngle = (
                                polynomial.a * absd * absd * absd
                                + polynomial.b * absd * absd 
                                + polynomial.c * absd 
                                + polynomial.d
                                )
                                / radius;
                var o = new Vector2(0.5f, origin.y + (delta > 0 ? radius : -radius));
                var r_angle = (origin.x - 0.5f) * totalAngle;
                return new Vector2(0.5f + Mathf.Sin(r_angle) * radius, o.y - Mathf.Sign(delta) * Mathf.Cos(r_angle) * radius);
            }
        }
    }

    private (float a, float b, float c , float d)
        GetFunctionSolutionInDegree(
            (float y, float xInDegree) xy01,
            (float y, float xInDegree) xy02,
            (float y, float xInDegree) xy03, 
            (float y, float xInDegree) xy04)
    {
        return GetFunctionSolution(
            (xy01.y, 0.5f * xy01.xInDegree / 90),
            (xy02.y, 0.5f * xy02.xInDegree / 90),
            (xy03.y, 0.5f * xy03.xInDegree / 90),
            (xy04.y, 0.5f * xy04.xInDegree / 90)
            );
    }

    private (float a, float b, float c)
        GetFunctionSolution(
            (float y, float x) xy01,
            (float y, float x) xy02,
            (float y, float x) xy03)
    {
        Matrix4x4 mat = new Matrix4x4();
        //f(x) = a x^3 + b x^2 + c x + d
        Vector4 answer;
        SetPolynomialRow(ref mat, 0, xy01.x);
        SetPolynomialRow(ref mat, 1, xy02.x);
        SetPolynomialRow(ref mat, 2, xy03.x);

        mat.SetRow(3, new Vector4(1, 0, 0, 0));

        answer = mat.inverse * new Vector4(xy01.y, xy02.y, xy03.y, 0);
        return (answer[1], answer[2], answer[3]);
    }

    private (float a, float b, float c, float d)
        GetFunctionSolution(
            (float y, float x) xy01,
            (float y, float x) xy02,
            (float y, float x) xy03,
            (float y, float x) xy04)
    {
        Matrix4x4 mat = new Matrix4x4();
        //f(x) = a x^3 + b x^2 + c x + d
        Vector4 answer;
        SetPolynomialRow(ref mat, 0, xy01.x);
        SetPolynomialRow(ref mat, 1, xy02.x);
        SetPolynomialRow(ref mat, 2, xy03.x);
        SetPolynomialRow(ref mat, 3, xy04.x);

        answer = mat.inverse * new Vector4(xy01.y, xy02.y, xy03.y, xy04.y);
        return (answer[0], answer[1], answer[2], answer[3]);
    }

    private void SetPolynomialRow(ref Matrix4x4 mat, int index, float x)
    {
        mat.SetRow(index, new Vector4(x * x * x, x * x, x, 1));
    }
    
}
